/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows.Forms;
using System.Text;
using System.Configuration;
using System.IO;
using EnvDTE;
using EnvDTE80;
using Microsoft.Win32;
using Microsoft.VisualStudio.CommandBars;
using DaveSexton.DocProject.Resources;
using DaveSexton.DocProject.Configuration;

namespace DaveSexton.DocProject
{
	public static class DocProjectEnvironment
	{
		#region Public Properties
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1802:UseLiteralsWhereAppropriate")]
		public static readonly string DocProjectRegistryRoot = @"Software\Dave Sexton\DocProject";

		public static BuildEngineProvider DefaultBuildEngineProvider
		{
			[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
			get
			{
				return defaultProvider;
			}
			[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				defaultProvider = value;

				addin.DefaultBuildEngineProvider = defaultProvider.Name;
				addin.SaveChanges();
			}
		}

		public static BuildEngineProviderCollection BuildEngineProviders
		{
			[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
			get
			{
				return providerCollection;
			}
		}

		public static bool RunningInVisualStudio
		{
			[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
			get
			{
				return runningInVS;
			}
		}

		public static string InstallPath
		{
			[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
			get
			{
				// Do not check initialized field here since this property can be initialized safely in other AppDomains
				// without the DocProject configuration loaded (Help2CompilerLoader uses this property and the SandcastleBuildEngine
				// creates an instance of that class for the build process component to use, which executes in a separate AppDomain).

				if (installPath == null)
				{
					using (RegistryKey key = Registry.LocalMachine.OpenSubKey(DocProjectRegistryRoot, false)
						?? Registry.CurrentUser.OpenSubKey(DocProjectRegistryRoot, false))
					{
						if (key == null)
							throw new InvalidOperationException(Errors.InstallPathNotFoundInRegistry);

						installPath = key.GetValue("InstallPath", null, RegistryValueOptions.DoNotExpandEnvironmentNames) as string;

						if (!string.IsNullOrEmpty(installPath))
						{
							installPath = Environment.ExpandEnvironmentVariables(installPath);

							if (string.IsNullOrEmpty(installPath) || !Directory.Exists(installPath))
								throw new InvalidOperationException(Errors.InstallPathRegistryValueInvalid);
						}
						else
							throw new InvalidOperationException(Errors.InstallPathRegistryValueInvalid);
					}
				}

				return installPath;
			}
		}

		public static bool IsInitialized
		{
			[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
			get
			{
				return initialized;
			}
		}

		public static bool IsUninstalling
		{
			[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
			get
			{
				return uninstalling;
			}
			[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
			[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode",
				Justification = "This property is assigned by the InstallActions assembly during uninstallation.  InternalsVisibleToAttribute allows this.")]
			internal set
			{
				uninstalling = value;
			}
		}

		public static IDocProject SelectedProject
		{
			[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
			get
			{
				if (runningInVS)
				{
					List<Project> projects = new List<Project>(VSEnvironmentHost.SelectedProjects);

					return (projects.Count == 1) ? DocProjectEnvironment.GetDocProject(projects[0]) : null;
				}
				else
					return selectedProject;
			}
			[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
			set
			{
				if (runningInVS)
					throw new InvalidOperationException(Errors.SelectedProjectAssignedInDte);

				selectedProject = value;
			}
		}

		public static string ProgId
		{
			[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
			get
			{
				// when the InstallActions project uninstalls each provider's commands the vsAddin field will be null so
				// the progid must be generated automatically - as of VS 2005 the full type name is used internally by VS
				return (vsAddin == null) ? typeof(DocProjectAddIn).FullName : vsAddin.ProgID;
			}
		}

		public static MessageBoxOptions MessageBoxOptions
		{
			get
			{
				if (System.Globalization.CultureInfo.CurrentUICulture.TextInfo.IsRightToLeft)
					return MessageBoxOptions.RightAlign | MessageBoxOptions.RtlReading;
				else
					return (MessageBoxOptions) 0;
			}
		}

		public static ToolBarCollection ToolBars
		{
			[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
			get
			{
				return toolBars;
			}
		}

		public static ToolWindowCollection ToolWindows
		{
			[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
			get
			{
				return toolWindows;
			}
		}

		public static MenuCommandCollection MenuCommands
		{
			[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
			get
			{
				return commands;
			}
		}

		public static IEnvironmentHost Host
		{
			[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
			get
			{
				return host;
			}
		}

		public static IWin32Window MainWindow
		{
			[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
			get
			{
				return (host == null) ? null : host.MainWindow;
			}
		}

		public static IEnumerable<IDocProject> Projects
		{
			[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
			get
			{
				return projects.AsReadOnly();
			}
		}

		public static Version DocProjectVersion
		{
			get
			{
				return typeof(DocProjectAddIn).Assembly.GetName().Version;
			}
		}
		#endregion

		#region Private / Protected
		/// <summary>
		/// References the selected project for when the Add-In is not running inside Visual Studio.
		/// </summary>
		private static IDocProject selectedProject;
		private static BuildEngineProvider defaultProvider;
		private static BuildEngineProviderCollection providerCollection;
		private static string installPath;
		private static System.Configuration.Configuration config;
		private static bool initialized, uninstalling;
		private static AddinSection addin;
		private static IEnvironmentHost host;
		private static readonly MenuCommandCollection commands = new MenuCommandCollection();
		private static readonly ToolBarCollection toolBars = new ToolBarCollection();
		private static readonly ToolWindowCollection toolWindows = new ToolWindowCollection();
		private static readonly List<IDocProject> projects = new List<IDocProject>(8);
		private static readonly List<DependencyLoader> dependencies = new List<DependencyLoader>(32);

		internal static readonly int progIdPrefixLength = (typeof(DocProjectAddIn).FullName + ".").Length;
		internal static volatile AddIn vsAddin;
		internal static volatile bool runningInVS;
		#endregion

		#region Methods
		#region Initialization
		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static ConfigurationSection GetConfigurationSection(string name)
		{
			if (config == null)
				// do not check initialized field here because this method may be 
				// called during initialization, but only after config is set.
				throw new InvalidOperationException(Errors.AddInNotInitialized);

			if (name == null)
				throw new ArgumentNullException("name");

			ConfigurationSection section = config.GetSection(name);

			if (section == null)
				throw new ArgumentException(string.Format(System.Globalization.CultureInfo.CurrentCulture,
					Errors.ConfigurationSectionNotFound, name));

			return section;
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		internal static void Initialize(DTE2 dte)
		{
			if (initialized)
				return;

			VSEnvironmentHost.DTE = dte;

			Initialize();
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static void Initialize(IEnvironmentHost hostUI)
		{
			if (hostUI == null)
				throw new ArgumentNullException("hostUI");

			if (initialized)
			{
				if (host != hostUI)
					throw new InvalidOperationException(Errors.InitializedWithDifferentHost);
			}
			else
			{
				host = hostUI;
				host.ProjectBuildComplete += host_ProjectBuildComplete;
				host.ProjectClosed += host_ProjectClosed;
				host.ProjectOpened += host_ProjectOpened;
				host.ProjectSaved += host_ProjectSaved;

				Initialize();
			}
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static void Initialize()
		{
			if (initialized)
				return;

			string configFile = Path.Combine(InstallPath, @"bin\" + Path.GetFileName(System.Reflection.Assembly.GetExecutingAssembly().Location) + ".config");

			if (!File.Exists(configFile))
				throw new InvalidOperationException(string.Format(System.Globalization.CultureInfo.CurrentCulture, Errors.MissingConfiguration, configFile));

			ExeConfigurationFileMap map = new ExeConfigurationFileMap();
			map.ExeConfigFilename = configFile;

			config = System.Configuration.ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);

			addin = (AddinSection) config.GetSection("addin");

			if (addin.DefaultBuildEngineProvider == null || addin.BuildEngineProviders == null || addin.BuildEngineProviders.Count < 1)
				return;

			dependencies.Clear();

			if (addin.Dependencies != null && addin.Dependencies.Count > 0)
			{
				foreach (DependencyElement dependency in addin.Dependencies)
					dependencies.Add(new DependencyLoader(Environment.ExpandEnvironmentVariables(dependency.Assembly)));
			}
			
			providerCollection = new BuildEngineProviderCollection();

			System.Web.Configuration.ProvidersHelper.InstantiateProviders(addin.BuildEngineProviders, providerCollection, typeof(BuildEngineProvider));
			providerCollection.SetReadOnly();

			defaultProvider = providerCollection[addin.DefaultBuildEngineProvider];

			if (defaultProvider == null)
			{
				foreach (BuildEngineProvider provider in providerCollection)
				{
					defaultProvider = provider;
					break;
				}
			}

			initialized = true;

			if (!uninstalling)
				InitializeVSExtensions();
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static void Uninitialize()
		{
			if (!initialized)
				return;

			try
			{
				if (uninstalling)
					UninstallProvidersAndVSCommands();
				else
					UninitializeVSExtensions();
			}
			finally
			{
				config = null;
				addin = null;
				installPath = null;
				defaultProvider = null;

				initialized = false;

				if (host != null)
				{
					host.ProjectBuildComplete -= host_ProjectBuildComplete;
					host.ProjectClosed -= host_ProjectClosed;
					host.ProjectOpened -= host_ProjectOpened;
					host.ProjectSaved -= host_ProjectSaved;

					host = null;
				}
			}
		}

		private static void InitializeVSExtensions()
		{
			if (runningInVS)
			{
				ToolWindow propertiesWindow = DocProjectPropertiesToolWindow.Instance;

				if (!toolWindows.Contains(propertiesWindow.Guid))
					toolWindows.Add(propertiesWindow);	// the Add method will call RegisterToolWindow

				MenuCommand propertiesCommand = DocProjectPropertiesMenuCommand.Instance;

				if (!commands.Contains(propertiesCommand.Name))
					commands.Add(propertiesCommand);		// the Add method will call RegisterCommand
			}

			if (providerCollection != null)
			{
				foreach (BuildEngineProvider provider in providerCollection)
				{
					provider.Load();

					if (runningInVS)
						InitializeVSProviderExtensions(provider);
				}
			}
		}

		private static void UninitializeVSExtensions()
		{
			if (runningInVS)
				toolWindows.Remove(DocProjectPropertiesToolWindow.ToolWindowGuid);		// the Remove method will call UnregisterToolWindow

			if (providerCollection != null)
			{
				foreach (BuildEngineProvider provider in providerCollection)
				{
					if (runningInVS)
						UninitializeVSProviderExtensions(provider);

					provider.Unload();
				}

				providerCollection = null;
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private static void InitializeVSProviderExtensions(BuildEngineProvider provider)
		{
			if (provider.HasToolBars)
			{
				foreach (ToolBar toolBar in provider.ToolBars)
				{
					// check for duplicates BY KEY in case of derived-providers such as Sandcastle/Deployment
					if (!toolBars.Contains(toolBar.Name))
					{
						try
						{
							toolBars.Add(toolBar);		// the Add method will call RegisterToolBar
						}
						catch (Exception ex)
						{
							Log.Exception(ex, Errors.CannotLoadToolbar, toolBar.Name);
						}
					}
				}
			}

			if (provider.HasToolWindows)
			{
				foreach (ToolWindow window in provider.ToolWindows)
				{
					if (!toolWindows.Contains(window.Guid))
					{
						try
						{
							toolWindows.Add(window);		// the Add method will call RegisterToolWindow
						}
						catch (Exception ex)
						{
							Log.Exception(ex, Errors.CannotLoadToolWindow, window.Guid);
						}
					}
				}
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private static void UninitializeVSProviderExtensions(BuildEngineProvider provider)
		{
			if (provider.HasToolBars)
			{
				foreach (ToolBar toolBar in provider.ToolBars)
				{
					try
					{
						toolBars.Remove(toolBar);		// the Remove method will call UnregisterToolBar
					}
					catch (Exception ex)
					{
						Log.Exception(ex, Errors.CannotUnloadToolbar, toolBar.Name);
					}
				}
			}

			if (provider.HasToolWindows)
			{
				foreach (ToolWindow window in provider.ToolWindows)
				{
					try
					{
						toolWindows.Remove(window);		// the Remove method will call UnregisterToolWindow
					}
					catch (Exception ex)
					{
						Log.Exception(ex, Errors.CannotUnloadToolWindow, window.Guid);
					}
				}
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private static void UninstallProvidersAndVSCommands()
		{
			Commands2 collection = (Commands2) VSEnvironmentHost.DTE.Commands;

			// NOTE: Unique name must be used for uninstallation (see comments below)
			RemoveCommand(collection, DocProjectPropertiesMenuCommand.Instance.UniqueName);

			if (providerCollection == null)
				return;

			foreach (BuildEngineProvider provider in providerCollection)
			{
				string name = null;

				try
				{
					try
					{
						name = provider.Name;

						if (provider.HasToolBars)
						{
							foreach (ToolBar toolBar in provider.ToolBars)
							{
								foreach (ToolBarCommand command in toolBar.Commands)
								{
									// NOTE: The RemoveCommand(VSCommand) method cannot be used since the commands were never 
									// initialized for this session of VS (because the uninstalling flag was set).  In other 
									// words, command.Command and command.Control will always be null, but UniqueName won't.
									RemoveCommand(collection, command.UniqueName);
								}
							}
						}
					}
					catch (Exception ex)
					{
						Log.Exception(ex, Errors.UninstallProvidersError, Environment.NewLine, name);
					}
					finally
					{
						provider.Uninstall();
					}
				}
				catch (Exception ex)
				{
					Log.Exception(ex, Errors.UninstallProvidersError, Environment.NewLine, name);
				}
			}
		}
		#endregion

		#region Providers
		/// <summary>
		/// Gets the first registered <see cref="BuildEngineProvider" /> of the specified type, <typeparamref name="TProvider" />.
		/// </summary>
		/// <param name="checkDerivedTypes">Indicates whether the first derived provider found should be returned if an exact match isn't found.</param>
		/// <typeparam name="TProvider">Type of <see cref="BuildEngineProvider" /> that this method will try to find.</typeparam>
		/// <remarks>When the <paramref name="checkDerivedTypes" /> argument is <b>true</b> a derived type is only returned if an exact match isn't found.</remarks>
		/// <returns>Registered <see cref="BuildEngineProvider" /> of the specified type, <typeparamref name="TProvider" /> or a derived type.</returns>
		/// <exception cref="ArgumentException">A provider of the specified type, or even a derived type if <paramref name="checkDerivedTypes" /> is <b>true</b>, 
		/// does not exist.</exception>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static TProvider GetProvider<TProvider>(bool checkDerivedTypes)
			where TProvider : BuildEngineProvider
		{
			if (!initialized)
				throw new InvalidOperationException(Errors.AddInNotInitialized);

			TProvider derivedProvider = null;

			foreach (BuildEngineProvider provider in providerCollection)
			{
				if (provider.GetType() == typeof(TProvider))
					return (TProvider) provider;
				else if (checkDerivedTypes && derivedProvider == null && typeof(TProvider).IsAssignableFrom(provider.GetType()))
					return derivedProvider = (TProvider) provider;
			}

			if (derivedProvider != null)
				return derivedProvider;

			throw new ArgumentException(string.Format(System.Globalization.CultureInfo.CurrentCulture, Errors.ProviderNotFound, typeof(TProvider)), "TProvider");
		}

		/// <summary>
		/// Gets the first registered <see cref="BuildEngineProvider" /> of the specified type, <typeparamref name="TProvider" />, 
		/// with the specified <paramref name="providerName" />.
		/// </summary>
		/// <remarks>This method returns a null reference when a provider that meets the search criteria isn't found.</remarks>
		/// <param name="providerName">Name of the provider that this method will try to find.</param>
		/// <typeparam name="TProvider">Type of <see cref="BuildEngineProvider" /> that this method will try to find.</typeparam>
		/// <returns>Registered <see cref="BuildEngineProvider" /> of the specified type, <typeparamref name="TProvider" />, 
		/// with the specified <paramref name="providerName" />.</returns>
		/// <exception cref="ArgumentNullException">The specified <paramref name="providerName" /> is a null reference 
		/// (<b>Nothing</b> in Visual Basic).</exception>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter"),
		System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2223:MembersShouldDifferByMoreThanReturnType"),
		System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static TProvider GetProvider<TProvider>(string providerName)
			where TProvider : BuildEngineProvider
		{
			if (!initialized)
				throw new InvalidOperationException(Errors.AddInNotInitialized);

			if (providerName == null)
				throw new ArgumentNullException("providerName");

			foreach (BuildEngineProvider provider in providerCollection)
				if (provider.GetType() == typeof(TProvider) && provider.Name.Equals(providerName, StringComparison.OrdinalIgnoreCase))
					return (TProvider) provider;

			return null;
		}

		/// <summary>
		/// Gets the registered <see cref="BuildEngineProvider" /> with the specified <paramref name="providerName" />.
		/// </summary>
		/// <remarks>This method returns a null reference when a provider that meets the search criteria isn't found.</remarks>
		/// <param name="providerName">Name of the provider that this method will try to find.</param>
		/// <returns>Registered <see cref="BuildEngineProvider" /> with the specified <paramref name="providerName" />.</returns>
		/// <exception cref="ArgumentNullException">The specified <paramref name="providerName" /> is a null reference 
		/// (<b>Nothing</b> in Visual Basic).</exception>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2223:MembersShouldDifferByMoreThanReturnType"),
		System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static BuildEngineProvider GetProvider(string providerName)
		{
			if (!initialized)
				throw new InvalidOperationException(Errors.AddInNotInitialized);

			if (providerName == null)
				throw new ArgumentNullException("providerName");

			foreach (BuildEngineProvider provider in providerCollection)
				if (provider.Name.Equals(providerName, StringComparison.OrdinalIgnoreCase))
					return provider;

			return null;
		}

		/// <summary>
		/// Gets the registered <see cref="BuildEngineProvider" /> for the specified DocProject as an automation object.
		/// </summary>
		/// <param name="provider"><see cref="Project"/> object that is the DocProject for which this method will return its provider.</param>
		/// <returns>Registered <see cref="BuildEngineProvider" /> for the specified <paramref name="project" />.</returns>
		/// <exception cref="ArgumentNullException">The specified <paramref name="provider" /> is <strong>null</strong>.</exception>
		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static BuildEngineProvider GetProvider(object project)
		{
			if (!initialized)
				throw new InvalidOperationException(Errors.AddInNotInitialized);

			if (project == null)
				throw new ArgumentNullException("project");

			IDocProject docProject = DocProjectEnvironment.GetDocProject(project);

			if (docProject != null)
				return docProject.Provider;
			else
				throw new ArgumentException(Errors.InvalidProjectNotDocProject, "project");
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		internal static ProviderSettings FindProviderSettings(BuildEngineProvider provider)
		{
			foreach (ProviderSettings settings in addin.BuildEngineProviders)
				if (settings.Name.Equals(provider.Name, StringComparison.OrdinalIgnoreCase))
					return settings;

			return null;
		}
		#endregion

		#region Projects
		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static IDocProject GetDocProject(object project)
		{
			if (project == null)
				throw new ArgumentNullException("project");

			IDocProject docProject = null;

			if (ProjectFactory.IsDocProject(project))
			{
				docProject = projects.Find(delegate(IDocProject currentProject)
				{
					return currentProject.Matches(project);
				});

				if (docProject == null)
				{
					docProject = (IDocProject) ProjectFactory.CreateProject(project, false);
					docProject.ReferenceAdded += new EventHandler<SystemPathEventArgs>(docProject_ReferenceAdded);
					docProject.ReferenceRemoved += new EventHandler<SystemPathEventArgs>(docProject_ReferenceRemoved);
					docProject.ReferenceChanged += new EventHandler<SystemPathEventArgs>(docProject_ReferenceChanged);

					projects.Add(docProject);

					System.Diagnostics.Debug.WriteLine("Registered project: " + docProject.Name, "DocProject");
				}
			}

			return docProject;
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static bool RemoveProject(IDocProject docProject)
		{
			if (docProject == null)
				throw new ArgumentNullException("docProject");

			docProject = projects.Find(delegate(IDocProject project)
				// find the matching reference that has been cached, if one exists
			{
				return docProject.Matches(project);
			});

			if (docProject != null)
			{
				System.Diagnostics.Debug.WriteLine("Unregistered project: " + docProject.Name, "DocProject");

				return projects.Remove(docProject);
			}
			else
				return false;
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static void ClearProjects()
		{
			// NOTE: The automation projects will not be available if the solution has closed; therefore, there's no need
			// to write to the debug window. (i.e., this method is called when a solution closes, so don't be tempted 
			// to call RemoveProject from here).
			projects.Clear();
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static IAnyProject FindProjectByFile(string projectFilePath)
		{
			if (projectFilePath == null)
				throw new ArgumentNullException("projectFilePath");

			foreach (Project project in VSEnvironmentHost.DTE.Solution.Projects)
			{
				switch (project.Kind)
				{
					case Constants.vsProjectKindSolutionItems:
						// solution folder
						if (project.ProjectItems != null)
						{
							foreach (IAnyProject anyProject in ForEachProjectRecursive(project.ProjectItems))
								if (projectFilePath.Equals(anyProject.FilePath, StringComparison.OrdinalIgnoreCase))
									return anyProject;
						}
						break;
					default:
						if (ProjectFactory.IsProject(project) && projectFilePath.Equals(project.FullName, StringComparison.OrdinalIgnoreCase))
						{
							IDocProject docProject = GetDocProject(project);

							if (docProject != null)
								return docProject;
							else
								return ProjectFactory.CreateProject(project);
						}

						break;
				}
			}

			return null;
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static IAnyProject FindProject(string projectUniqueName)
		{
			if (projectUniqueName == null)
				throw new ArgumentNullException("projectUniqueName");

			foreach (Project project in VSEnvironmentHost.DTE.Solution.Projects)
			{
				switch (project.Kind)
				{
					case Constants.vsProjectKindSolutionItems:
						// solution folder
						if (project.ProjectItems != null)
						{
							foreach (IAnyProject anyProject in ForEachProjectRecursive(project.ProjectItems))
								if (projectUniqueName.Equals(anyProject.UniqueName, StringComparison.OrdinalIgnoreCase))
									return anyProject;
						}
						break;
					default:
						if (ProjectFactory.IsProject(project) && projectUniqueName.Equals(project.UniqueName, StringComparison.OrdinalIgnoreCase))
						{
							IDocProject docProject = GetDocProject(project);

							if (docProject != null)
								return docProject;
							else
								return ProjectFactory.CreateProject(project);
						}

						break;
				}
			}

			return null;
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static IEnumerable<IAnyProject> FindProjects(Predicate<IAnyProject> predicate)
		{
			return new MarshalByRefEnumerable<IAnyProject>(FindProjectsInternal, predicate);
		}

		private static IEnumerable<IAnyProject> FindProjectsInternal(object[] arguments)
		{
			Predicate<IAnyProject> predicate = (Predicate<IAnyProject>) arguments[0];

			foreach (Project project in VSEnvironmentHost.DTE.Solution.Projects)
			{
				switch (project.Kind)
				{
					case Constants.vsProjectKindSolutionItems:
						// solution folder
						if (project.ProjectItems != null)
						{
							foreach (IAnyProject childProject in ForEachProjectRecursive(project.ProjectItems))
								if (predicate == null || predicate(childProject))
									yield return childProject;
						}
						break;
					default:
						if (ProjectFactory.IsProject(project))
						{
							IAnyProject anyProject = null;
							IDocProject docProject = GetDocProject(project);

							if (docProject != null)
								anyProject = docProject;
							else
								anyProject = ProjectFactory.CreateProject(project);

							if (anyProject != null && (predicate == null || predicate(anyProject)))
								yield return anyProject;
						}
						break;
				}
			}
		}

		private static IEnumerable<IAnyProject> ForEachProjectRecursive(ProjectItems items)
		{
			foreach (ProjectItem item in items)
			{
				if (item.SubProject != null)
				{
					Project project = item.SubProject as Project;

					if (project != null)
					{
						if (ProjectFactory.IsProject(project))
						{
							IDocProject docProject = GetDocProject(project);

							if (docProject != null)
								yield return docProject;
							else
								yield return ProjectFactory.CreateProject(project);
						}
						else if (project.ProjectItems != null)
						// yep, it's this ridiculous :)
						{
							foreach (IAnyProject projectChild in ForEachProjectRecursive(project.ProjectItems))
								if (projectChild != null)
									yield return projectChild;
						}
					}
				}
				else if (item.ProjectItems != null)
				{
					foreach (IAnyProject project in ForEachProjectRecursive(item.ProjectItems))
						if (project != null)
							yield return project;
				}
			}
		}
		#endregion

		#region General
		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static object GetService(Type serviceType)
		{
			if (serviceType == typeof(IDocProjectHost))
			{
				IDocProject project = SelectedProject;
				DocProjectOptions options = (project == null) ? null : project.Options;
				DaveSexton.DocProject.Engine.IBuildEngine engine = (project == null) ? null : BuildController.GetEngineForProject(project);
				BuildSettings settings = (engine == null) ? null : engine.Settings;

				return new DocProjectHost(project, engine, settings, options, host, runningInVS);
			}
			else if (host != null)
			{
				object service = host.GetService(serviceType);

				if (service != null)
					return service;
			}

			if (serviceType == typeof(System.ComponentModel.Design.IComponentChangeService))
				return ComponentChangeService.Instance;
			else
				return null;
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static void ShowTrace()
		{
			if (host != null)
				host.ShowTrace();
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static void ShowErrorDialog(string messageFormat, params object[] messageFormatArgs)
		{
			System.Windows.Forms.MessageBox.Show(MainWindow,
				string.Format(System.Globalization.CultureInfo.CurrentCulture, messageFormat, messageFormatArgs), Resources.Text.DocProject,
				System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error, System.Windows.Forms.MessageBoxDefaultButton.Button1,
				MessageBoxOptions);
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static DialogResult ShowErrorDialog(MessageBoxButtons buttons, string messageFormat, params object[] messageFormatArgs)
		{
			return System.Windows.Forms.MessageBox.Show(MainWindow,
				string.Format(System.Globalization.CultureInfo.CurrentCulture, messageFormat, messageFormatArgs), Resources.Text.DocProject,
				buttons, System.Windows.Forms.MessageBoxIcon.Error, System.Windows.Forms.MessageBoxDefaultButton.Button1,
				MessageBoxOptions);
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static DialogResult ShowErrorDialog(MessageBoxButtons buttons, MessageBoxDefaultButton defaultButton, string messageFormat, params object[] messageFormatArgs)
		{
			return System.Windows.Forms.MessageBox.Show(MainWindow,
				string.Format(System.Globalization.CultureInfo.CurrentCulture, messageFormat, messageFormatArgs), Resources.Text.DocProject,
				buttons, System.Windows.Forms.MessageBoxIcon.Error, defaultButton, MessageBoxOptions);
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static void ShowErrorDialog(Exception ex, string messageFormat, params object[] messageFormatArgs)
		{
			System.Windows.Forms.MessageBox.Show(MainWindow,
				FormatErrorMessage(ex, messageFormat, messageFormatArgs), Resources.Text.DocProject,
				System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error, System.Windows.Forms.MessageBoxDefaultButton.Button1,
				MessageBoxOptions);
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static DialogResult ShowErrorDialog(Exception ex, MessageBoxButtons buttons, string messageFormat, params object[] messageFormatArgs)
		{
			return System.Windows.Forms.MessageBox.Show(MainWindow,
				FormatErrorMessage(ex, messageFormat, messageFormatArgs), Resources.Text.DocProject,
				buttons, System.Windows.Forms.MessageBoxIcon.Error, System.Windows.Forms.MessageBoxDefaultButton.Button1,
				MessageBoxOptions);
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static DialogResult ShowErrorDialog(Exception ex, MessageBoxButtons buttons, MessageBoxDefaultButton defaultButton, string messageFormat, params object[] messageFormatArgs)
		{
			return System.Windows.Forms.MessageBox.Show(MainWindow,
				FormatErrorMessage(ex, messageFormat, messageFormatArgs), Resources.Text.DocProject,
				buttons, System.Windows.Forms.MessageBoxIcon.Error, defaultButton, MessageBoxOptions);
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static void ShowErrorDialogWithCaption(string caption, string messageFormat, params object[] messageFormatArgs)
		{
			System.Windows.Forms.MessageBox.Show(MainWindow,
				string.Format(System.Globalization.CultureInfo.CurrentCulture, messageFormat, messageFormatArgs), caption,
				System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error, System.Windows.Forms.MessageBoxDefaultButton.Button1,
				MessageBoxOptions);
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static DialogResult ShowErrorDialogWithCaption(MessageBoxButtons buttons, string caption, string messageFormat, params object[] messageFormatArgs)
		{
			return System.Windows.Forms.MessageBox.Show(MainWindow,
				string.Format(System.Globalization.CultureInfo.CurrentCulture, messageFormat, messageFormatArgs), caption,
				buttons, System.Windows.Forms.MessageBoxIcon.Error, System.Windows.Forms.MessageBoxDefaultButton.Button1,
				MessageBoxOptions);
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static DialogResult ShowErrorDialogWithCaption(MessageBoxButtons buttons, MessageBoxDefaultButton defaultButton, string caption, string messageFormat, params object[] messageFormatArgs)
		{
			return System.Windows.Forms.MessageBox.Show(MainWindow,
				string.Format(System.Globalization.CultureInfo.CurrentCulture, messageFormat, messageFormatArgs), caption,
				buttons, System.Windows.Forms.MessageBoxIcon.Error, defaultButton, MessageBoxOptions);
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static void ShowErrorDialogWithCaption(Exception ex, string caption, string messageFormat, params object[] messageFormatArgs)
		{
			System.Windows.Forms.MessageBox.Show(MainWindow,
				FormatErrorMessage(ex, messageFormat, messageFormatArgs), caption,
				System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error, System.Windows.Forms.MessageBoxDefaultButton.Button1,
				MessageBoxOptions);
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static DialogResult ShowErrorDialogWithCaption(Exception ex, MessageBoxButtons buttons, string caption, string messageFormat, params object[] messageFormatArgs)
		{
			return System.Windows.Forms.MessageBox.Show(MainWindow,
				FormatErrorMessage(ex, messageFormat, messageFormatArgs), caption,
				buttons, System.Windows.Forms.MessageBoxIcon.Error, System.Windows.Forms.MessageBoxDefaultButton.Button1,
				MessageBoxOptions);
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static DialogResult ShowErrorDialogWithCaption(Exception ex, MessageBoxButtons buttons, MessageBoxDefaultButton defaultButton, string caption, string messageFormat, params object[] messageFormatArgs)
		{
			return System.Windows.Forms.MessageBox.Show(MainWindow,
				FormatErrorMessage(ex, messageFormat, messageFormatArgs), caption,
				buttons, System.Windows.Forms.MessageBoxIcon.Error, defaultButton, MessageBoxOptions);
		}

		private static string FormatErrorMessage(Exception ex, string format, params object[] args)
		{
			if (args != null)
				format = string.Format(System.Globalization.CultureInfo.CurrentCulture, format, args);

			if (ex == null)
				return format;
			else
			{
				if (!string.IsNullOrEmpty(format))
					return format + Environment.NewLine + Environment.NewLine + ex.Message;
				else
					return ex.Message;
			}
		}
		#endregion

		#region Commands
		[System.Diagnostics.DebuggerHidden]
		private static Command GetCommandAndControl(CommandBar parent, VSCommand command, out CommandBarControl control)
		{
			Command vsCommand = null;
			control = null;

			try
			{
				Commands2 collection = (Commands2) VSEnvironmentHost.DTE.Commands;

				vsCommand = collection.Item(command.UniqueName, -1);

				// NOTE: There does not appear to be any way to discover the associations between VS commands and controls with complete certainty.
				// In testing, the CommandBarControl.Tag, Parameter and DescriptionText properties are not persisted when VS is closed.  The only values 
				// that are persisted seem to be the control's type, which cannot be used to identify it, and the control's caption, which techincally 
				// can change depending upon UI state, which makes it a poor candidate.  The "Id" property seems to always be zero (0) for controls 
				// created through VS automation, so at least it can be used to reduce the number of candidate controls when searching.
				// The CommandBar.FindControl method _does_ work when used with a "tag", although as stated previously tags are lost when VS is closed.
				// The workaround below is to iterate over the controls in search of one that has an Id of 0 and a matching caption.  If the caption
				// for the control does not match then it will not be located and a duplicate control will be added automatically by the caller.  For this
				// reason it's recommended that the VSCommand.GetStatusText property is only overriden by derived classes when the text might change 
				// depending upon user-interaction.  This method is invoked when the Add-In is first loaded so user-interaction should not affect the 
				// value of the caption.
				foreach (CommandBarControl child in parent.Controls)
				{
					if (child.Id == 0 && string.Equals(child.Caption, command.Text, StringComparison.OrdinalIgnoreCase))
					{
						control = child;
						break;
					}
				}
			}
			catch (ArgumentException) { }

			return vsCommand;
		}

		private static object[] GetCommandContextGuids(VSCommand command)
		{
			ICollection<Guid> guidsCollection = command.ContextGuids;
			object[] guids;

			if (guidsCollection == null)
				guids = new object[0];
			else
			{
				guids = new object[guidsCollection.Count];

				int g = 0;
				foreach (Guid guid in guidsCollection)
					guids[g++] = guid;
			}

			return guids;
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		internal static void RemoveCommand(VSCommand command)
		{
			Command vsCommand = command.Command;

			command.Command = null;
			command.Control = null;

			if (vsCommand != null)
				vsCommand.Delete();
		}

		/// <remarks>
		/// Used for uninstalling commands that have not been initialized for the current VS session.
		/// The <see cref="RemoveCommand(VSCommand)"/> method cannot be used since the <c>Command</c> property of each
		/// command instance will be null if <see cref="IsUninstalling"/> is <strong>true</strong>.
		/// </remarks>
		private static void RemoveCommand(Commands2 collection, string uniqueName)
		{
			try
			{
				EnvDTE.Command vsCommand = collection.Item(uniqueName, -1);

				if (vsCommand != null)
					vsCommand.Delete();
			}
			catch (ArgumentException) { }
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		internal static void AddCommand(ToolBarCommand command)
		{
			if (uninstalling)
				return;
			
			CommandBarControl control;
			Command vsCommand = GetCommandAndControl(command.ToolBar.CommandBar, command, out control);

			// NOTE: Tool bars are removed when VS is closed and so their controls are automatically destroyed.  In testing, "vsCommand" is never null after the first
			// time it's created since VS persists commands, but "control" is always null each time VS is started since the tool bar and its controls were destroyed 
			// by DocProject.

			if (vsCommand == null)
			{
				object[] guids = GetCommandContextGuids(command);

				Commands2 allCommands = (Commands2) VSEnvironmentHost.DTE.Commands;

				vsCommand = allCommands.AddNamedCommand2(vsAddin, command.Name, command.Text, command.Tooltip, true,
					(command.UseOfficeBitmapResourceId) ? command.OfficeBitmapResourceId : 1, ref guids,
					(int) (vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled), (int) command.Style, command.ControlType);
			}

			if (control == null)
			{
				control = (CommandBarControl) vsCommand.AddControl(command.ToolBar.CommandBar, command.ToolBar.CommandBar.Controls.Count + 1);
				// NOTE: Tag, Parameter and DescriptionText cannot be used to identify the control since they are not persisted when VS is closed

				if (command.ControlType == vsCommandControlType.vsCommandControlTypeButton && !command.UseOfficeBitmapResourceId)
				{
					CommandBarButton button = (CommandBarButton) control;

					System.Drawing.Image image = command.Image;

					if (image != null)
					{
						button.Picture = ImageTranslator.GetIPictureDisp(image);

						System.Drawing.Color transparencyKey = command.ImageTransparencyKey;

						if (transparencyKey != System.Drawing.Color.Transparent && transparencyKey != System.Drawing.Color.Empty)
							button.Mask = ImageTranslator.GetIPictureDispMask(image,
								System.Drawing.Color.White, System.Drawing.Color.Black, transparencyKey);
					}
				}
			}

			command.Command = vsCommand;
			command.Control = control;

			command.OnCommandCreated(EventArgs.Empty);
		}

		private static void AddCommand(MenuCommand command)
		{
			if (uninstalling)
				return;

			CommandBarControl control;
			Command vsCommand = GetCommandAndControl(command.Parent, command, out control);

			// NOTE: In testing, "vsCommand" and "control" are never null after the first time they're created since VS persists commands and controls that are not explicitly 
			// removed before VS is closed.  Unlike tool bars and tool windows, DocProject does not remove commands (including tool bar commands) so that users can assign them 
			// keyboard shortcuts.  Since MenuCommand instances are not torn down when DocProject uninitializes, their controls seem to be automatically persisted by VS as well.
			// Unfortunately though CommandBarControl.Tag is not persisted so it's difficult to identify a command's control after it's created for the first time.  The chosen 
			// workaround is to check for Id equals zero (0), which is true for all custom commands, and then check to see if the control's caption matches.

			if (vsCommand == null)
			{
				object[] guids = GetCommandContextGuids(command);

				Commands2 allCommands = (Commands2) VSEnvironmentHost.DTE.Commands;

				vsCommand = allCommands.AddNamedCommand2(vsAddin, command.Name, command.Text, command.Tooltip, true,
					(command.UseOfficeBitmapResourceId) ? command.OfficeBitmapResourceId : 1, ref guids,
					(int) (vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled), (int) command.Style, command.ControlType);
			}

			if (control == null)
			{
				control = (CommandBarControl) vsCommand.AddControl(command.Parent, (command.Location < 1) ? command.Parent.Controls.Count + 1 : command.Location);
				// NOTE: Tag, Parameter and DescriptionText cannot be used to identify the control since they are not persisted when VS is closed

				if (command.ControlType == vsCommandControlType.vsCommandControlTypeButton && !command.UseOfficeBitmapResourceId)
				{
					CommandBarButton button = (CommandBarButton) control;

					System.Drawing.Image image = command.Image;

					if (image != null)
					{
						button.Picture = ImageTranslator.GetIPictureDisp(image);

						System.Drawing.Color transparencyKey = command.ImageTransparencyKey;

						if (transparencyKey != System.Drawing.Color.Transparent && transparencyKey != System.Drawing.Color.Empty)
							button.Mask = ImageTranslator.GetIPictureDispMask(image,
								System.Drawing.Color.White, System.Drawing.Color.Black, transparencyKey);
					}
				}
			}

			command.Command = vsCommand;
			command.Control = control;

			command.OnCommandCreated(EventArgs.Empty);
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		internal static void QueryCommandStatus(string name, vsCommandStatusTextWanted textWanted, ref vsCommandStatus status, ref object text)
		{
			if (commands.Contains(name))
			{
				MenuCommand command = commands[name];

				try
				{
					switch (textWanted)
					{
						case vsCommandStatusTextWanted.vsCommandStatusTextWantedNone:
							status = command.Status;
							break;
						case vsCommandStatusTextWanted.vsCommandStatusTextWantedStatus:
							text = command.GetStatusText(status);
							break;
						case vsCommandStatusTextWanted.vsCommandStatusTextWantedName:
							text = command.Name;
							break;
					}
				}
				catch (Exception ex)
				{
					Log.Exception(ex, Resources.Text.QueryStatusError, Environment.NewLine, name, command.Parent.NameLocal);
				}
			}
			else
			{
				foreach (ToolBar toolBar in toolBars)
				{
					try
					{
						toolBar.QueryStatus(name, textWanted, ref status, ref text);
					}
					catch (Exception ex)
					{
						Log.Exception(ex, Resources.Text.QueryStatusError, Environment.NewLine, name, toolBar.Name);

						break;
					}
				}
			}
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		internal static object ExecuteCommand(string name, vsCommandExecOption option, object argument, out bool handled)
		{
			if (commands.Contains(name))
			{
				MenuCommand command = commands[name];

				handled = true;

				try
				{
					return command.Execute(option, argument);
				}
				catch (Exception ex)
				{
					Log.Exception(ex, "Menu command execution error.  Command: {0}.", name);

					MessageBox.Show(DocProjectEnvironment.MainWindow, string.Format(System.Globalization.CultureInfo.CurrentCulture,
						Resources.Text.ExecuteCommandError, Environment.NewLine, name, command.Parent.NameLocal, ex.Message),
						Resources.Text.ExecuteCommandErrorCaption, MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1,
						DocProjectEnvironment.MessageBoxOptions);
				}
			}
			else
			{
				handled = false;

				foreach (ToolBar toolBar in DocProjectEnvironment.ToolBars)
				{
					try
					{
						object result = toolBar.Execute(name, option, argument, out handled);

						if (handled)
							return result;
					}
					catch (Exception ex)
					{
						Log.Exception(ex, "Command execution error.  Command: {0}; Tool bar: {1}.", name, toolBar.Name);

						MessageBox.Show(DocProjectEnvironment.MainWindow, string.Format(System.Globalization.CultureInfo.CurrentCulture,
							Resources.Text.ExecuteCommandError, Environment.NewLine, name, toolBar.Name, ex.Message),
							Resources.Text.ExecuteCommandErrorCaption, MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1,
							DocProjectEnvironment.MessageBoxOptions);

						break;
					}
				}
			}

			return null;
		}
		#endregion

		#region Toolbars
		[System.Diagnostics.DebuggerHidden]
		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		internal static void RegisterToolBar(ToolBar item)
		{
			if (uninstalling)
				return;

			string name = item.Name;
			CommandBar commandBar;

			try
			{
				commandBar = VSEnvironmentHost.GetCommandBar(name);
			}
			// one user reported this error on CodePlex (discussion:11689) - possibly a bug in Visual Studio
			catch (NullReferenceException)
			{
				commandBar = VSEnvironmentHost.AddToolBar(name, item.Position);
			}
			catch (System.ArgumentException)
			// thrown when the commandbar doesn't exist
			{
				commandBar = VSEnvironmentHost.AddToolBar(name, item.Position);
			}

			// setting this property will raise the ToolBar.CommandBarAttached event
			item.CommandBar = commandBar;

			if (item.PersistUserChanges)
				LoadToolBarSettings(item);
			else
			{
				commandBar.RowIndex = item.RowIndex;
				commandBar.Enabled = item.Enabled;
				commandBar.Visible = item.Visible;
			}

			foreach (ToolBarCommand command in item.Commands)
				AddCommand(command);
		}

		[System.Diagnostics.DebuggerHidden]
		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		internal static void UnregisterToolBar(ToolBar item)
		{
			if (!uninstalling)
			{
				if (item.PersistUserChanges)
					SaveToolBarSettings(item);

				if (item.RemoveCommandsOnUnload)
					foreach (ToolBarCommand command in item.Commands)
						RemoveCommand(command);
			}

			try
			{
				if (item.CommandBar != null)
					item.CommandBar.Delete();
			}
			// execption occurred in testing when the user had already deleted the command bar using the Visual Studio "Customize..." dialog
			catch (System.Runtime.InteropServices.COMException) { }

			// setting this property to null will raise the ToolBar.CommandBarDetached event
			item.CommandBar = null;
		}

		private static void LoadToolBarSettings(ToolBar item)
		{
			string name = item.Name;
			CommandBar bar = item.CommandBar;
			System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.InvariantCulture;

			bar.Visible = item.Provider.Settings.Read(name + "_Visible", culture, item.Visible);
			bar.Position = item.Provider.Settings.Read(name + "_Position", culture, item.Position);
			bar.RowIndex = item.Provider.Settings.Read(name + "_RowIndex", culture, item.RowIndex);
			bar.Top = item.Provider.Settings.Read(name + "_Top", culture, 0);
			bar.Left = item.Provider.Settings.Read(name + "_Left", culture, 0);
		}

		private static void SaveToolBarSettings(ToolBar item)
		{
			string name = item.Name;
			CommandBar bar = item.CommandBar;
			System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.InvariantCulture;

			try
			{
				item.Provider.Settings.Write(name + "_Visible", bar.Visible, culture);
				item.Provider.Settings.Write(name + "_Position", bar.Position, culture);
				item.Provider.Settings.Write(name + "_RowIndex", bar.RowIndex, culture);
				item.Provider.Settings.Write(name + "_Top", bar.Top, culture);
				item.Provider.Settings.Write(name + "_Left", bar.Left, culture);
			}
			// occurred in testing for an unknown reason (only once though and it was in the VS 2008 Experimental Hive)
			catch (System.Runtime.InteropServices.COMException ex)
			{
				Log.Exception(ex, "Error retrieving settings while unloading a provider command bar: " + name);
			}

			item.Provider.CommitProviderSettings();
		}
		#endregion

		#region Menus
		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		internal static void RegisterMenuCommand(MenuCommand command)
		{
			if (!uninstalling)
				AddCommand(command);
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		internal static void UnregisterMenuCommand(MenuCommand command)
		{
			if (!uninstalling)
				RemoveCommand(command);
		}
		#endregion

		#region Tool Windows
		[System.Diagnostics.DebuggerHidden]
		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		internal static void RegisterToolWindow(ToolWindow item)
		{
			if (uninstalling)
				return;

			Guid guid = item.Guid;
			Window2 window;

			try
			{
				window = VSEnvironmentHost.GetToolWindow(guid);
			}
			catch (System.ArgumentException)
			// thrown when the tool window doesn't exist
			{
				Windows2 windows = (Windows2) VSEnvironmentHost.DTE.Windows;
				Control control = item.Control;

				if (control == null)
					throw new InvalidOperationException(string.Format(System.Globalization.CultureInfo.CurrentCulture,
						Errors.ToolWindowNoControl, guid));

				Control surface = null;
				Type type = typeof(Control);
				object objRef = surface;

				window = (Window2) windows.CreateToolWindow2(vsAddin, type.Assembly.FullName, type.FullName, item.Caption, guid.ToString("B"), ref objRef);

				// read the reference back since it has been updated by VS
				surface = (Control) objRef;

				window.IsFloating = item.IsFloating;
				window.Linkable = item.IsLinkable;

				System.Drawing.Bitmap bitmap = item.Image;

				if (bitmap != null)
				{
					if (VSEnvironmentHost.DefaultInstance.Version == "8.0")
						window.SetTabPicture(bitmap.GetHbitmap());
					else
						window.SetTabPicture(ImageTranslator.GetIPictureDisp(bitmap));
				}

				control.Dock = DockStyle.Fill;

				surface.Controls.Add(control);

				if (item.ShowOnLoad)
					window.Visible = true;
			}

			// setting this property will raise the ToolWindow.WindowCreated event
			item.Window = window;
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		internal static void UnregisterToolWindow(ToolWindow item)
		{
			try
			{
				if (item.Window != null)
					item.Window.Close(vsSaveChanges.vsSaveChangesNo);
			}
			// in case the tool window was already closed by some other means (such as a macro)
			catch (System.Runtime.InteropServices.COMException) { }

			// setting this property to null will raise the ToolWindow.WindowDestroyed event if Window is already a non-null value
			item.Window = null;
		}
		#endregion
		#endregion

		#region Event Handlers
		private static void host_ProjectBuildComplete(object sender, DocProjectEventArgs e)
		{
			OnProjectBuildComplete(e);
		}

		private static void host_ProjectClosed(object sender, DocProjectEventArgs e)
		{
			OnProjectClosed(e);
		}

		private static void host_ProjectOpened(object sender, DocProjectEventArgs e)
		{
			OnProjectOpened(e);
		}

		private static void host_ProjectSaved(object sender, DocProjectEventArgs e)
		{
			OnProjectSaved(e);
		}

		private static void docProject_ReferenceChanged(object sender, SystemPathEventArgs e)
		{
			BuildController.GetEngineForProject(e.Project).BuildType = DaveSexton.DocProject.Engine.BuildType.Full;

			OnProjectReferenceChanged(e);
		}

		private static void docProject_ReferenceRemoved(object sender, SystemPathEventArgs e)
		{
			BuildController.GetEngineForProject(e.Project).BuildType = DaveSexton.DocProject.Engine.BuildType.Full;

			OnProjectReferenceRemoved(e);
		}

		private static void docProject_ReferenceAdded(object sender, SystemPathEventArgs e)
		{
			BuildController.GetEngineForProject(e.Project).BuildType = DaveSexton.DocProject.Engine.BuildType.Full;

			OnProjectReferenceAdded(e);
		}
		#endregion

		#region Events
		private static readonly object ProjectSavedEventLock = new object();
		private static EventHandler<DocProjectEventArgs> ProjectSavedEvent;
		private static readonly object ProjectOpenedEventLock = new object();
		private static EventHandler<DocProjectEventArgs> ProjectOpenedEvent;
		private static readonly object ProjectClosedEventLock = new object();
		private static EventHandler<DocProjectEventArgs> ProjectClosedEvent;
		private static readonly object ProjectBuildCompleteEventLock = new object();
		private static EventHandler<DocProjectEventArgs> ProjectBuildCompleteEvent;
		private static readonly object ProjectReferenceAddedEventLock = new object();
		private static EventHandler<SystemPathEventArgs> ProjectReferenceAddedEvent;
		private static readonly object ProjectReferenceRemovedEventLock = new object();
		private static EventHandler<SystemPathEventArgs> ProjectReferenceRemovedEvent;
		private static readonly object ProjectReferenceChangedEventLock = new object();
		private static EventHandler<SystemPathEventArgs> ProjectReferenceChangedEvent;

		/// <summary>
		/// Event raised when a source reference of an <see cref="IDocProject"/> implementation has changed.
		/// </summary>
		public static event EventHandler<SystemPathEventArgs> ProjectReferenceChanged
		{
			add
			{
				lock (ProjectReferenceChangedEventLock)
				{
					ProjectReferenceChangedEvent += value;
				}
			}
			remove
			{
				lock (ProjectReferenceChangedEventLock)
				{
					ProjectReferenceChangedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="ProjectReferenceChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="SystemPathEventArgs" /> object that provides the arguments for the event.</param>
		private static void OnProjectReferenceChanged(SystemPathEventArgs e)
		{
			EventHandler<SystemPathEventArgs> handler = null;

			lock (ProjectReferenceChangedEventLock)
			{
				handler = ProjectReferenceChangedEvent;
			}

			if (handler != null)
				handler(null, e);
		}

		/// <summary>
		/// Event raised when a source reference is removed from an <see cref="IDocProject"/> implementation.
		/// </summary>
		public static event EventHandler<SystemPathEventArgs> ProjectReferenceRemoved
		{
			add
			{
				lock (ProjectReferenceRemovedEventLock)
				{
					ProjectReferenceRemovedEvent += value;
				}
			}
			remove
			{
				lock (ProjectReferenceRemovedEventLock)
				{
					ProjectReferenceRemovedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="ProjectReferenceRemoved" /> event.
		/// </summary>
		/// <param name="e"><see cref="SystemPathEventArgs" /> object that provides the arguments for the event.</param>
		private static void OnProjectReferenceRemoved(SystemPathEventArgs e)
		{
			EventHandler<SystemPathEventArgs> handler = null;

			lock (ProjectReferenceRemovedEventLock)
			{
				handler = ProjectReferenceRemovedEvent;
			}

			if (handler != null)
				handler(null, e);
		}

		/// <summary>
		/// Event raised when a source reference is added to an <see cref="IDocProject"/> implementation.
		/// </summary>
		public static event EventHandler<SystemPathEventArgs> ProjectReferenceAdded
		{
			add
			{
				lock (ProjectReferenceAddedEventLock)
				{
					ProjectReferenceAddedEvent += value;
				}
			}
			remove
			{
				lock (ProjectReferenceAddedEventLock)
				{
					ProjectReferenceAddedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="ProjectReferenceAdded" /> event.
		/// </summary>
		/// <param name="e"><see cref="SystemPathEventArgs" /> object that provides the arguments for the event.</param>
		private static void OnProjectReferenceAdded(SystemPathEventArgs e)
		{
			EventHandler<SystemPathEventArgs> handler = null;

			lock (ProjectReferenceAddedEventLock)
			{
				handler = ProjectReferenceAddedEvent;
			}

			if (handler != null)
				handler(null, e);
		}

		/// <summary>
		/// Event raised after a DocProject has been built.
		/// </summary>
		public static event EventHandler<DocProjectEventArgs> ProjectBuildComplete
		{
			add
			{
				lock (ProjectBuildCompleteEventLock)
				{
					ProjectBuildCompleteEvent += value;
				}
			}
			remove
			{
				lock (ProjectBuildCompleteEventLock)
				{
					ProjectBuildCompleteEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="ProjectBuildComplete" /> event.
		/// </summary>
		/// <param name="e"><see cref="DocProjectEventArgs" /> object that provides the arguments for the event.</param>
		private static void OnProjectBuildComplete(DocProjectEventArgs e)
		{
			EventHandler<DocProjectEventArgs> handler = null;

			lock (ProjectBuildCompleteEventLock)
			{
				handler = ProjectBuildCompleteEvent;
			}

			if (handler != null)
				handler(null, e);
		}

		/// <summary>
		/// Event raised when a DocProjct is closed.
		/// </summary>
		public static event EventHandler<DocProjectEventArgs> ProjectClosed
		{
			add
			{
				lock (ProjectClosedEventLock)
				{
					ProjectClosedEvent += value;
				}
			}
			remove
			{
				lock (ProjectClosedEventLock)
				{
					ProjectClosedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="ProjectClosed" /> event.
		/// </summary>
		/// <param name="e"><see cref="DocProjectEventArgs" /> object that provides the arguments for the event.</param>
		private static void OnProjectClosed(DocProjectEventArgs e)
		{
			EventHandler<DocProjectEventArgs> handler = null;

			lock (ProjectClosedEventLock)
			{
				handler = ProjectClosedEvent;
			}

			if (handler != null)
				handler(null, e);
		}

		/// <summary>
		/// Event raised when a DocProject is opened.
		/// </summary>
		public static event EventHandler<DocProjectEventArgs> ProjectOpened
		{
			add
			{
				lock (ProjectOpenedEventLock)
				{
					ProjectOpenedEvent += value;
				}
			}
			remove
			{
				lock (ProjectOpenedEventLock)
				{
					ProjectOpenedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="ProjectOpened" /> event.
		/// </summary>
		/// <param name="e"><see cref="DocProjectEventArgs" /> object that provides the arguments for the event.</param>
		private static void OnProjectOpened(DocProjectEventArgs e)
		{
			EventHandler<DocProjectEventArgs> handler = null;

			lock (ProjectOpenedEventLock)
			{
				handler = ProjectOpenedEvent;
			}

			if (handler != null)
				handler(null, e);
		}

		/// <summary>
		/// Event raised when a DocProject is saved.
		/// </summary>
		public static event EventHandler<DocProjectEventArgs> ProjectSaved
		{
			add
			{
				lock (ProjectSavedEventLock)
				{
					ProjectSavedEvent += value;
				}
			}
			remove
			{
				lock (ProjectSavedEventLock)
				{
					ProjectSavedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="ProjectSaved" /> event.
		/// </summary>
		/// <param name="e"><see cref="DocProjectEventArgs" /> object that provides the arguments for the event.</param>
		private static void OnProjectSaved(DocProjectEventArgs e)
		{
			EventHandler<DocProjectEventArgs> handler = null;

			lock (ProjectSavedEventLock)
			{
				handler = ProjectSavedEvent;
			}

			if (handler != null)
				handler(null, e);
		}
		#endregion
	}
}
